// Controller 父结构体
// 每个业务的 controller 直接内嵌 BaseController，不用直接引用 beego.Controller
// @Author 

package base

import (
	"dhfshop/models/consts"
	"dhfshop/models/org"
	"dhfshop/service/authz"
	"dhfshop/service/nsqcli"
	"dhfshop/service/reauthorize"
	"dhfshop/service/redisgo"
	"dhfshop/utils/helper"
	"dhfshop/utils/mapstruct"
	"dhfshop/utils/response"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/validation"
	"github.com/deckarep/golang-set"
	"net/http"
	"strconv"
	"strings"
	"time"
)

var MapTopAuthUrl map[string]string

type BaseController struct {
	beego.Controller
	CurrentUser *org.AuthenticatedUser
}

func sendTopAuthMsg(name, identity string, storeId, memberId int64, isMember int) {
	topAuthNsq := struct {
		Name     string `json:"name"`
		Identity string `json:"identity"`
		MemberId int64  `json:"member_id"`
		IsMember int    `json:"is_member"`
		StoreId  int64  `json:"store_id"`
		CurTime  int64  `json:"time"`
	}{Name: name, Identity: identity, StoreId: storeId, MemberId: memberId, IsMember: isMember}
	topAuthNsq.CurTime = time.Now().UTC().Unix()
	if b, err := json.Marshal(topAuthNsq); err == nil {
		address := beego.AppConfig.String("NsqUrl")
		topic := "topAuth"
		if err = nsqcli.StartProducer(address, topic, b); err != nil {
			beego.Debug("StartProducer err:", err.Error())
		}
	}
}

//此名称因为是重载，不需要显式调用
func (this *BaseController) Prepare() {
	url := this.Ctx.Input.URL()
	if ok := strings.HasPrefix(url, "/public/"); ok {
		return
	}
	if ok := strings.HasPrefix(url, "/ws/"); ok {
		return
	}
	if ok := strings.HasPrefix(url, "/wsauto/"); ok {
		return
	}
	if !this.CheckPermission(this.Ctx.Request) {
		//this.RequirePermission(this.Ctx.ResponseWriter)
		this.Ctx.Output.SetStatus(403) //设置错误状态码
		this.ServeError("Permission is out of range")
		this.StopRun()
		return
	}
	if this.Ctx.Input.GetData("curUserId") == nil {
		this.Ctx.Output.SetStatus(403) //设置错误状态码
		this.ServeError("curUserId is null")
		this.StopRun()
		return
	}
	curPartnerId := this.Ctx.Input.GetData("curCompanyId").(int)
	curSysRoleId := this.Ctx.Input.GetData("curRoleId").(int)
	curUserId := int64(this.Ctx.Input.GetData("curUserId").(int))
	if name, ok := MapTopAuthUrl[url]; ok {
		err := this.VerifyReauthorize(url, int64(curPartnerId), int64(curUserId))
		if err != nil {
			//发送异步消息
			var curStoreId int64 = 0
			if curSysRoleId == 1 {
				tmpStoreId := this.Ctx.Input.Header("dhfmallstoreid")
				if len(tmpStoreId) > 0 {
					curStoreId, _ = strconv.ParseInt(tmpStoreId, 10, 64)
				}
			}
			if curSysRoleId == 2 {
				tmpStoreId := this.Ctx.Input.Header("dhfbackstoreid")
				if len(tmpStoreId) > 0 {
					curStoreId, _ = strconv.ParseInt(tmpStoreId, 10, 64)
				}
			}
			sendTopAuthMsg(name, url, curStoreId, curUserId, curSysRoleId)
			//
			this.Ctx.Output.SetStatus(406)
			this.ServeError(err.Error())
			this.StopRun()
			return
		}
	}
	if curSysRoleId == 1 {
		return
	}
	if curSysRoleId == 4 {
		return
	}
	if curSysRoleId == 9 {
		return
	}
	curSubRoleId := this.Ctx.Input.GetData("curSubRoleId").(int)
	currentUser, err := org.GetAuthUserFromCache(int64(curPartnerId), int64(curSubRoleId), int64(curUserId))
	if err != nil {
		this.Ctx.Output.SetStatus(401) //设置错误状态码
		this.ServeError(err.Error())
		this.StopRun()
		return
	}
	identity, action := this.GetIdAction(url)
	_, err = org.VerifyRolePermit(currentUser, identity, action)
	if err != nil {
		if err.Error() == "ORDER_VERSION_IS_ERROR" {
			this.Ctx.Output.SetStatus(409) //定义特殊的状态码
		} else {
			this.Ctx.Output.SetStatus(403) //设置错误状态码
		}
		this.ServeError(err.Error())
		this.StopRun()
		return
	}
	this.CurrentUser = currentUser
}

func (this *BaseController) GetIdAction(url string) (string, string) {
	pos := strings.LastIndexAny(url, "/")
	idstr := helper.Substr(url, 0, pos+1)
	action := helper.Substr(url, pos+1, 20)
	return idstr, action
}

//似乎没法处理时间格式的查询，时间中含有冒号
// fields: col1,col2,entity.col3; query: k:v,k:v; subquery: k:v,k:v; order_by: k:v,k:v
func (this *BaseController) GetQueryPara() (map[string]string, map[string]string, []string, map[string]string, int, int, error) {
	query := make(map[string]string)
	subquery := make(map[string]string)
	fields := []string{}
	order_by := make(map[string]string)
	offset := 0 //default is 0
	limit := 10 //default is 50
	v := struct {
		Fields        string `form:"fields"`
		PageSize      int    `form:"page_size"`
		PageNo        int    `form:"page_no"`
		StartCreated  string `form:"start_created"` //去掉的前提是query中时间格式是yyyyMMddHHmmss
		EndCreated    string `form:"end_created"`
		StartSourceAt string `form:"start_source_at"` //去掉的前提是query中时间格式是yyyyMMddHHmmss
		EndSourceAt   string `form:"end_source_at"`
		Name          string `form:"name"`  //不能去掉，title中可能含有逗号，分号等特殊字符
		Query         string `form:"query"` //时间格式 yyyyMMddHHmmss,不能是yyyy-MM-dd HH:mm:ss
		Subquery      string `form:"subquery"`
		OrderBy       string `form:"order_by"`
	}{PageSize: 10, PageNo: 1}
	this.ParseForm(&v)
	valid := validation.Validation{}
	valid.Range(v.PageSize, 1, 1000, "page_size")
	valid.Range(v.PageNo, 1, 1000, "page_no")
	passed, _ := valid.Valid(&v)
	if !passed {
		err := errors.New(valid.Errors[0].Key + " " + valid.Errors[0].Message)
		return query, subquery, fields, order_by, offset, limit, err
	}
	v.PageNo = v.PageNo - 1 //为了计算offset
	if len(v.Fields) > 2 {
		fields = strings.Split(v.Fields, ",")
	}
	//beego.Debug("PeriodList Query", v.Query)
	if len(v.Query) > 2 {
		for _, cond := range strings.Split(v.Query, ",") {
			kv := strings.Split(cond, ":")
			if len(kv) != 2 {
				err := errors.New("Error: invalid query key/value pair")
				return query, subquery, fields, order_by, offset, limit, err
			}
			k, v := kv[0], kv[1]
			query[k] = v
		}
	}
	if len(v.StartCreated) == 19 {
		if startCreatedTime, err := time.Parse(consts.DATETIME_FORMAT, v.StartCreated); err == nil {
			query["start_created"] = startCreatedTime.Format(consts.DATELINKTIME_FORMAT)
		}
	}
	if len(v.EndCreated) == 19 {
		if endCreatedTime, err := time.Parse(consts.DATETIME_FORMAT, v.EndCreated); err == nil {
			query["end_created"] = endCreatedTime.Format(consts.DATELINKTIME_FORMAT)
		}
	}
	if len(v.StartSourceAt) == 10 {
		query["start_source_at"] = v.StartSourceAt
	}
	if len(v.EndSourceAt) == 10 {
		query["end_source_at"] = v.EndSourceAt
	}
	if len(v.Name) > 0 {
		query["title.icontains"] = v.Name //似乎目前只有商品会用到
	}
	if len(v.Subquery) > 2 {
		for _, cond := range strings.Split(v.Subquery, ",") {
			kv := strings.Split(cond, ":")
			if len(kv) != 2 {
				err := errors.New("Error: invalid subquery key/value pair")
				return query, subquery, fields, order_by, offset, limit, err
			}
			k, v := kv[0], kv[1]
			subquery[k] = v
		}
	}
	if len(v.OrderBy) > 2 {
		for _, cond := range strings.Split(v.OrderBy, ",") {
			kv := strings.Split(cond, ":")
			if len(kv) != 2 {
				err := errors.New("Error: invalid order_by key/value pair")
				return query, subquery, fields, order_by, offset, limit, err
			}
			k, v := kv[0], kv[1]
			order_by[k] = v
		}
	}
	offset = v.PageNo * v.PageSize
	limit = v.PageSize
	return query, subquery, fields, order_by, offset, limit, nil
}

func (this *BaseController) GetUpdateFields(s interface{}, reqJson, tagName string, exclude []string) ([]string, error) {
	structKeys := mapset.NewSet()
	vals := mapstruct.Struct2Map(s, tagName)
	for key, _ := range vals {
		structKeys.Add(key)
	}
	formKeys := mapset.NewSet()
	if tagName == "form" {
		for key, _ := range this.Ctx.Request.Form {
			formKeys.Add(key)
		}
	} else if tagName == "json" {
		reqJsonKey, _ := helper.GetJsonField(reqJson)
		for _, value := range reqJsonKey {
			formKeys.Add(value)
		}
	}
	interSect := structKeys.Intersect(formKeys)
	if interSect.Cardinality() == 0 {
		return nil, errors.New("set interSect should is empty")
	}
	exclueTemp := make([]interface{}, len(exclude))
	for i, v := range exclude {
		exclueTemp[i] = v
	}
	exclueSet := mapset.NewSetFromSlice(exclueTemp)
	resultSet := interSect.Difference(exclueSet)
	fieldsArr := []string{}
	for _, item := range resultSet.ToSlice() {
		fieldsArr = append(fieldsArr, fmt.Sprintf("%s", item))
	}
	return fieldsArr, nil
}

// 向客户端输出错误
func (this *BaseController) ServeError(msg string) {
	outPut := response.Reponse(1, nil, msg)
	this.Data["json"] = outPut
	this.ServeJSON()
}

func (this *BaseController) ServeCodeError(errCode string, errMsg string) {
	code, err := strconv.Atoi(errCode)
	if err != nil {
		code = -1
	}
	outPut := response.Reponse(code, nil, errMsg)
	this.Data["json"] = outPut
	this.ServeJSON()
}

func (this *BaseController) ServeOk(msg string) {
	outPut := response.Reponse(0, nil, msg)
	this.Data["json"] = outPut
	this.ServeJSON()
}

func (this *BaseController) CheckPermission(r *http.Request) bool {
	curRoleId := this.Ctx.Input.GetData("curRoleId").(int)
	user := strconv.Itoa(curRoleId)
	//user := this.GetUserName(r)
	method := r.Method
	path := r.URL.Path
	return authz.EnforcerIn.Enforce(user, path, method)
}

func (this *BaseController) RequirePermission(w http.ResponseWriter) {
	w.WriteHeader(403)
	w.Write([]byte("403 Forbidden\n"))
}

func (this *BaseController) GetUserName(r *http.Request) string {
	username, _, _ := r.BasicAuth()
	return username
}

func (this *BaseController) VerifyReauthorize(url string, partnerId, curUserId int64) error {

	redisconn := redisgo.GetInstance()
	partnerIdStr := strconv.FormatInt(partnerId, 10)
	memberIdStr := strconv.FormatInt(curUserId, 10)
	cacheKey := "toptoken_" + partnerIdStr + "_" + memberIdStr
	if cacheValue, err := redisconn.GetString(cacheKey); err == nil && len(cacheValue) > 5 {
		if e := reauthorize.CheckSign(cacheValue); e == nil {
			return nil
		} else {
			beego.Debug("CheckSign error:", e.Error())
		}
	} else {
		beego.Debug("redisconn error:", err.Error())
	}
	return errors.New("api: " + url + " 被拦截")
}
